#ifndef __ETLSTRING_H__
#define __ETLSTRING_H__

#ifndef __ETLARRAY_H__
	#error etlstring.h requires etlarray.h to be included first
#endif

#define LPCTYPE			const TYPE*
#define LPTYPE			TYPE*

inline int chVsprintf(LPSTR buffer, LPCSTR format, va_list argptr){ return vsprintf(buffer, format, argptr);}
inline int chVsprintf(LPWSTR buffer, LPCWSTR format, va_list argptr) { return vswprintf(buffer, format, argptr);}

inline BOOL chIsDigit(USHORT ch) { return ch >= '0' && ch <= '9'; }
inline BOOL chIsSpace(USHORT ch) { return (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t'); }
inline BOOL chIsAlpha(USHORT ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); }

template<class TYPE>
inline BOOL chIsLeadbyte(LPCTYPE szThis)
{
	chASSERT(szThis != NULL);
	if(sizeof(TYPE) == 1)
	{
		return mbtowc(NULL, (LPCSTR)szThis, 1) == -1 && szThis[1] != '\0';
		//return isleadbyte(szThis[0]) && szThis[1] != '\0';
	}
	return FALSE;
}

template<class TYPE>
inline BOOL chIsTrailbyte(LPCTYPE szHead, LPCTYPE szThis)
{
	chASSERT(szThis != NULL && szHead != NULL && szThis >= szHead);
	if(szThis <= szHead)
		return FALSE;
	if(sizeof(TYPE) == 1)
	{
		LPCTYPE szTemp = szThis - 1;
		
		if (!chIsLeadbyte(szTemp))
			return FALSE;
		
		while ((szHead <= --szTemp) && chIsLeadbyte(szTemp))
            ;
		
		return ((szThis - szTemp) & 0x01) == 0;
	}
	return FALSE;
}

template<class TYPE>
inline USHORT chWord(LPCTYPE szText)
{
	chASSERT(szText != NULL);
	if(chIsLeadbyte(szText))
		return chMAKEWORD(szText[0], szText[1]);
	return szText[0];
}

template<class TYPE>
inline LPCTYPE chStrinc(LPCTYPE szThis)
{
	chASSERT(szThis != NULL);
	if(szThis[0] == '\0')
		return NULL;
	if(chIsLeadbyte(szThis))
		return szThis + 2;
	return szThis + 1;
}

template<class TYPE>
inline LPCTYPE chStrdec(LPCTYPE szHead, LPCTYPE szThis)
{
	chASSERT(szThis != NULL && szHead != NULL && szThis >= szHead);
	if(szThis <= szHead)
		return NULL;
	if(chIsTrailbyte(szHead, szThis - 1))
		return szThis - 2;
	return szThis - 1;
}

inline USHORT chUpper(USHORT ch)
{
	if(ch >= 'a' && ch <= 'z')
		ch = ch - 'a' + 'A';
	return ch;
}

inline USHORT chLower(USHORT ch)
{
	if(ch >= 'A' && ch <= 'Z')
		ch = ch - 'A' + 'a';
	return ch;
}
inline USHORT chReverse(USHORT ch)
{
	if(ch >= 'a' && ch <= 'z')
		ch = ch - 'a' + 'A';
	else if(ch >= 'A' && ch <= 'Z')
		ch = ch - 'A' + 'a';
	return ch;
}

template<class TYPE, bool bMatchCase>
inline int chStrcmp(LPCTYPE s1, LPCTYPE s2)
{
	chASSERT(s1 != NULL && s2 != NULL);
	if(s1 == s2)
		return 0;
	for( ;*s1 != '\0' && *s2 != '\0'; s1 = chStrinc(s1), s2 = chStrinc(s2))
	{
		USHORT ch1 = chWord(s1);
		USHORT ch2 = chWord(s2);
		if(!bMatchCase)
		{
			ch1 = chUpper(ch1);
			ch2 = chUpper(ch2);
		}
		if(ch1 != ch2)
			return ch1 - ch2;
	}
	return *s1 - *s2;
}

template<class TYPE, bool bMatchCase>
inline int chStrncmp(LPCTYPE s1, LPCTYPE s2, int nCount)
{
	chASSERT(s1 != NULL && s2 != NULL && nCount >= 0);
	if(s1 == s2)
		return 0;

	for( ; ;)
	{
		if(nCount <= 0)
			return 0;

		if(*s1 == '\0' || *s2 == '\0')
			break;

		USHORT ch1 = chWord(s1);
		USHORT ch2 = chWord(s2);
		if(!bMatchCase)
		{
			ch1 = chUpper(ch1);
			ch2 = chUpper(ch2);
		}

		if(ch1 != ch2)
			return ch1 - ch2;
		s1 = chStrinc(s1);
		s2 = chStrinc(s2);
		nCount--;
	}
	return *s1 - *s2;
}

template<class TYPE, bool bMatchCase>
inline BOOL chIsBeginText(LPCTYPE string, LPCTYPE strCharSet)
{
	chASSERT(string != NULL && strCharSet != NULL);
	for( ;*strCharSet != '\0'; string = chStrinc(string), strCharSet = chStrinc(strCharSet))
	{
		USHORT ch1 = chWord(string);
		USHORT ch2 = chWord(strCharSet);
		if(!bMatchCase)
		{
			ch1 = chUpper(ch1);
			ch2 = chUpper(ch2);
		}

		if(ch1 != ch2)
			return FALSE;
	}
	return TRUE;
}

template<class TYPE, bool bMatchCase>
inline LPCTYPE chFind(LPCTYPE lpszSrc, USHORT ch)
{
	chASSERT(lpszSrc != NULL);
	for( ; ;lpszSrc = chStrinc(lpszSrc))
	{
		USHORT ch1 = chWord(lpszSrc);
		USHORT ch2 = ch;
		if(!bMatchCase)
		{
			ch1 = chUpper(ch1);
			ch2 = chUpper(ch2);
		}

		if(ch1 == ch2)
			return lpszSrc;
		if(*lpszSrc == '\0')
			break;
	}
	return NULL;
}

template<class TYPE, bool bMatchCase>
inline LPCTYPE chFind(LPCTYPE lpszSrc, LPCTYPE lpszSubText)
{
	chASSERT(lpszSrc != NULL);
	if(*lpszSubText == '\0' && *lpszSrc != '\0')
		return NULL;

	for( ; *lpszSrc != '\0';lpszSrc = chStrinc(lpszSrc))
	{
		LPCTYPE szTempText = lpszSubText;
		if(chIsBeginText<TYPE, bMatchCase>(lpszSrc, lpszSubText))
			return lpszSrc;
	}
	return NULL;
}

template<class TYPE, bool bMatchCase>
inline LPCTYPE chFindOneOf(LPCTYPE lpszSrc, LPCTYPE lpszTarget)
{
	chASSERT(lpszSrc != NULL && lpszTarget != NULL);
	if(*lpszTarget == '\0' && *lpszSrc != '\0')
		return NULL;

	for(; *lpszSrc != '\0'; lpszSrc = chStrinc(lpszSrc))
	{
		if(chFind<TYPE, bMatchCase>(lpszTarget, chWord(lpszSrc)) != NULL)
			return lpszSrc;
	}
	return NULL;
}

template<class TYPE, bool bMatchCase>
inline LPCTYPE chResverFindOneOf(LPCTYPE lpszSrc, LPCTYPE lpszEnding, LPCTYPE lpszTarget)
{
	chASSERT(lpszSrc != NULL && lpszEnding > lpszSrc && lpszTarget != NULL);
	if(*lpszTarget == '\0' && *lpszSrc != '\0')
		return NULL;

	for(; ;)
	{
		lpszEnding = chStrdec(lpszSrc, lpszEnding);
		if(lpszEnding == NULL)
			return NULL;

		if(chFind<TYPE, bMatchCase>(lpszTarget, chWord(lpszEnding)) != NULL)
			return lpszEnding;
	}
	return NULL;
}

template<class TYPE, bool bMatchCase>
inline LPCTYPE chSkipOneOf(LPCTYPE lpszSrc, LPCTYPE lpszSkip)
{
	chASSERT(lpszSrc != NULL && lpszSkip != NULL);
	for(; *lpszSrc != '\0'; lpszSrc = chStrinc(lpszSrc))
	{
		if(chFind<TYPE, bMatchCase>(lpszSkip, chWord(lpszSrc)) == NULL)
			break;
	}
	return lpszSrc;
}

template<class TYPE, bool bMatchCase>
inline LPCTYPE chResverSkipOneOf(LPCTYPE lpszSrc, LPCTYPE lpszEnding, LPCTYPE lpszSkip)
{
	chASSERT(lpszSrc != NULL && lpszEnding > lpszSrc && lpszSkip != NULL);
	for(; ;)
	{
		lpszEnding = chStrdec(lpszSrc, lpszEnding);
		if(lpszEnding == NULL)
			return lpszSrc;
		if(chFind<TYPE, bMatchCase>(lpszSkip, chWord(lpszEnding)) == NULL)
			return chStrinc(lpszEnding);
	}
	return lpszSrc;
}

template<class TYPE>
inline int chStrlen(LPCTYPE lpszText)
{
	return chFind<TYPE, true>(lpszText, (USHORT)'\0') - lpszText;
}

template<class TYPE>
inline void chStrcpy(LPTYPE lpszDes, LPCTYPE lpszSrc)
{
	chASSERT(lpszSrc != NULL && lpszDes != NULL);
	for( ;*lpszSrc != '\0';)
		*(lpszDes++) = *(lpszSrc++);
	*lpszDes = '\0';
}

template<class TYPE>
inline void chStrncpy(LPTYPE lpszDes, LPCTYPE lpszSrc, int nCount)
{
	chASSERT(lpszSrc != NULL && lpszDes != NULL);
	for( ;*lpszSrc != '\0' && nCount > 0; nCount--)
		*(lpszDes++) = *(lpszSrc++);
	*lpszDes = '\0';
}

template<class TYPE>
inline void chStrcat(LPTYPE lpszDes, LPCTYPE lpszSrc)
{
	chASSERT(lpszSrc != NULL && lpszDes != NULL);
	while(*lpszDes != '\0')
		lpszDes ++;
	for( ;*lpszSrc != '\0'; )
		*(lpszDes++) = *(lpszSrc++);
	*lpszDes = '\0';
}

//////////////////////////////////////////////////////////////////////////
template <class TYPE>
inline LPCTYPE chStrtod(LPCTYPE lpszSrc, double* pData)
{
	chASSERT(lpszSrc != NULL);
	LPCTYPE pEnd = NULL;
	double dData = 0;
	if(sizeof(TYPE) == 1)
	{
		dData = strtod((LPCSTR)lpszSrc, (LPTSTR*)&pEnd);
	}
	else
	{
		dData = wcstod((LPCWSTR)lpszSrc, (LPWSTR*)&pEnd);
	}
	if(pData != NULL)
		*pData = dData;
	return pEnd;
}

template <class TYPE>
inline LPCTYPE chStrtol(LPCTYPE lpszSrc, long* pData, int ibase)
{
	chASSERT(lpszSrc != NULL && ibase >= 2 && ibase < 36);
	LPCTYPE pEnd = NULL;
	long dData = 0;
	if(sizeof(TYPE) == 1)
	{
		dData = strtol((LPCSTR)lpszSrc, (LPSTR*)&pEnd, ibase);
	}
	else
	{
		dData = wcstol((LPCWSTR)lpszSrc, (LPWSTR*)&pEnd, ibase);
	}
	if(pData != NULL)
		*pData = dData;
	return pEnd;
}

template <class TYPE>
inline LPCTYPE chStrtoI64(LPCTYPE lpszSrc, INT64* pData, int iBase)
{
	chASSERT(lpszSrc != NULL && ibase >= 2 && ibase < 36);

	while (chIsSpace(chWord(lpszSrc)))
		lpszSrc = chStrinc(lpszSrc);
	
	BOOL bBlowZero = FALSE;
	switch(*lpszSrc)
	{
	case '-':
		bBlowZero = TRUE;
	case '+':
		lpszSrc = chStrinc(lpszSrc);
		break;
	}

	INT64 number = 0;
	for ( ;*lpszSrc != '\0'; )
	{
		WORD wThisWord = chWord(lpszSrc);
		int digit = -1;
		if (chIsDigit(wThisWord))
			digit = wThisWord - '0';
		else if(chIsAlpha(wThisWord))
			digit = chUpper(wThisWord) - 'A' + 10;

		if(digit != -1)
			break;
		number = number * iBase + digit;
		lpszSrc = chStrinc(lpszSrc);
	}
	
	if ( bBlowZero )
		number = -number;
	if(pData != NULL)
		*pData = number;
	
	return lpszSrc;
}

template<class TYPE>
inline int chAtoi(LPCTYPE lpszText)
{
	long data = 0;
	chStrtol(lpszText, &data, 10);
	return data;
}

template<class TYPE>
inline int etlPreFormatString(const TYPE* lpszFormat, va_list argList)
{
	chASSERT(lpszFormat != NULL);
	enum _FormatModifiers
	{
		FORCE_ANSI =	0x10000,
		FORCE_UNICODE =	0x20000,
		FORCE_INT64 =	0x40000
	};

	// make a guess at the maximum length of the resulting string
	int nMaxLen = 0;
	for (LPCTYPE lpsz = lpszFormat; *lpsz != '\0'; lpsz = chStrinc(lpsz))
	{
		// handle '%' character, but watch out for '%%'
		if (*lpsz != '%' || *(lpsz = chStrinc(lpsz)) == '%')
		{
			nMaxLen += chIsLeadbyte(lpsz) ? 2 : 1;
			continue;
		}

		
		int nItemLen = 0;
		
		// handle '%' character with format
		int nWidth = 0;
		for (; *lpsz != '\0'; lpsz = chStrinc(lpsz))
		{
			// check for valid flags
			if (*lpsz == '#')
				nMaxLen += 2;   // for '0x'
			else if (*lpsz == '*')
				nWidth = va_arg(argList, int);
			else if (*lpsz == '-' || *lpsz == '+' || *lpsz == '0' ||
				*lpsz == ' ')
				;
			else // hit non-flag character
				break;
		}
		// get width and skip it
		if (nWidth == 0)
		{
			// width indicated by
			nWidth = chAtoi(lpsz);
			for (; *lpsz != '\0' && chIsDigit(*lpsz); lpsz = chStrinc(lpsz))
				;
		}
		chASSERT(nWidth >= 0);
		
		int nPrecision = 0;
		if (*lpsz == '.')
		{
			// skip past '.' separator (width.precision)
			lpsz = chStrinc(lpsz);
			
			// get precision and skip it
			if (*lpsz == '*')
			{
				nPrecision = va_arg(argList, int);
				lpsz = chStrinc(lpsz);
			}
			else
			{
				nPrecision = chAtoi(lpsz);
				for (; *lpsz != '\0' && chIsDigit(*lpsz); lpsz = chStrinc(lpsz))
					;
			}
			chASSERT(nPrecision >= 0);
		}
		
		// should be on type modifier or specifier
		int nModifier = 0;
		if (chStrlen(lpsz) >= 3 && lpsz[0] == 'I' && lpsz[1] == '6' && lpsz[2] == '4')
		{
			lpsz += 3;
			nModifier = FORCE_INT64;
#if !defined(_X86_) && !defined(_ALPHA_)
			// __int64 is only available on X86 and ALPHA platforms
			chASSERT(FALSE);
#endif
		}
		else
		{
			switch (*lpsz)
			{
				// modifiers that affect size
			case 'h':
				nModifier = FORCE_ANSI;
				lpsz = chStrinc(lpsz);
				break;
			case 'l':
				nModifier = FORCE_UNICODE;
				lpsz = chStrinc(lpsz);
				break;
				
				// modifiers that do not affect size
			case 'F':
			case 'N':
			case 'L':
				lpsz = chStrinc(lpsz);
				break;
			}
		}
		
		// now should be on specifier
		switch (*lpsz | nModifier)
		{
			// single characters
		case 'c':
		case 'C':
			nItemLen = 2;
			va_arg(argList, TYPE);
			break;
		case 'c'|FORCE_ANSI:
		case 'C'|FORCE_ANSI:
			nItemLen = 2;
			va_arg(argList, CHAR);
			break;
		case 'c'|FORCE_UNICODE:
		case 'C'|FORCE_UNICODE:
			nItemLen = 2;
			va_arg(argList, WCHAR);
			break;
			
			// strings
		case 's':
			{
				LPCTYPE pstrNextArg = va_arg(argList, LPCTYPE);
				if (pstrNextArg == NULL)
					nItemLen = 6;  // "(null)"
				else
				{
					nItemLen = chStrlen(pstrNextArg);
					nItemLen = chMAX(1, nItemLen);
				}
			}
			break;
			
		case 'S':
			{
#ifndef _UNICODE
				LPWSTR pstrNextArg = va_arg(argList, LPWSTR);
				if (pstrNextArg == NULL)
					nItemLen = 6;  // "(null)"
				else
				{
					nItemLen = wcslen(pstrNextArg);
					nItemLen = chMAX(1, nItemLen);
				}
#else
				LPCSTR pstrNextArg = va_arg(argList, LPCSTR);
				if (pstrNextArg == NULL)
					nItemLen = 6; // "(null)"
				else
				{
					nItemLen = strlen(pstrNextArg);
					nItemLen = chMAX(1, nItemLen);
				}
#endif
			}
			break;
			
		case 's'|FORCE_ANSI:
		case 'S'|FORCE_ANSI:
			{
				LPCSTR pstrNextArg = va_arg(argList, LPCSTR);
				if (pstrNextArg == NULL)
					nItemLen = 6; // "(null)"
				else
				{
					nItemLen = strlen(pstrNextArg);
					nItemLen = chMAX(1, nItemLen);
				}
			}
			break;
			
		case 's'|FORCE_UNICODE:
		case 'S'|FORCE_UNICODE:
			{
				LPWSTR pstrNextArg = va_arg(argList, LPWSTR);
				if (pstrNextArg == NULL)
					nItemLen = 6; // "(null)"
				else
				{
					nItemLen = wcslen(pstrNextArg);
					nItemLen = chMAX(1, nItemLen);
				}
			}
			break;
		}
		
		// adjust nItemLen for strings
		if (nItemLen != 0)
		{
			if (nPrecision != 0)
				nItemLen = chMIN(nItemLen, nPrecision);
			nItemLen = chMAX(nItemLen, nWidth);
		}
		else
		{
			switch (*lpsz)
			{
				// integers
			case 'd':
			case 'i':
			case 'u':
			case 'x':
			case 'X':
			case 'o':
				if (nModifier & FORCE_INT64)
					va_arg(argList, INT64);
				else
					va_arg(argList, int);
				nItemLen = 32;
				nItemLen = chMAX(nItemLen, nWidth+nPrecision);
				break;
				
			case 'e':
			case 'g':
			case 'G':
				va_arg(argList, DOUBLE);
				nItemLen = 128;
				nItemLen = chMAX(nItemLen, nWidth+nPrecision);
				break;
				
			case 'f':
				va_arg(argList, DOUBLE);
				// nItemLen = 128; // width isn't truncated
				// 312 == strlen("-1+(309 zeroes).")
				// 309 zeroes == max precision of a double
				nItemLen = chMAX(128, 312 + nPrecision);
				break;
				
			case 'p':
				va_arg(argList, void*);
				nItemLen = 32;
				nItemLen = chMAX(nItemLen, nWidth+nPrecision);
				break;
				
				// no output
			case 'n':
				va_arg(argList, int*);
				break;
				
			default:
				chASSERT(FALSE);  // unknown formatting option
			}
		}
		// adjust nMaxLen for output nItemLen
		nMaxLen += nItemLen;
	}
	return nMaxLen;
}

//namespace ETL_StringHelper
//{
static BYTE NULL_STR[2] = {0, 0};

template <class TYPE>
class _chTString : protected _chObjArray<TYPE, 64>
{
protected:
	typedef _chObjArray<TYPE, 64>				type_TYPEArray;
	typedef _chTString<TYPE>					type_chTYPEString;
public:
	void Clear()
	{
		AllocTextSizeWithEnding(0);
		m_nTextLength = 0;
	}

	//////////////////////////////////////////////////////////////////////////
	// GetInfo
	BOOL IsEmpty() const
	{ return m_aT == NULL || *m_aT == '\0'; }
	//////////////////////////////////////////////////////////////////////////
	// data operactor
	operator LPCTYPE () const
	{ return m_aT == NULL ? (LPCTYPE)NULL_STR : m_aT; }
	//////////////////////////////////////////////////////////////////////////
	// Text buffer about
	LPTYPE GetBuffer(int nMinLength)
	{
		chASSERT(nMinLength >= 0);
		nMinLength = chMAX(nMinLength, GetLength());
		AllocTextSizeWithEnding(nMinLength);
		m_nTextLength = -1;
		return m_aT; 
	}
	LPTYPE GetBufferSetLength(int nLen)
	{
		chASSERT(nLen >= 0);
		RepeatFill('\0', nLen);
		m_nTextLength = -1;
		return m_aT; 
	}
	// text length before zero ending
	int GetLength()
	{
		if(m_nTextLength == -1)
			m_nTextLength = chStrlen(m_aT);

		chASSERT(m_nTextLength <= type_TYPEArray::GetSize());
		return m_nTextLength;
	}
	
	//////////////////////////////////////////////////////////////////////////
	// find & replace
	int Find(TYPE ch, int iFromIndex) const
	{
		chASSERT(iFromIndex >= 0 && GetLength() >= iFromIndex);
		chASSERT(!chIsTrailbyte((LPCTYPE)(*this), (LPCTYPE)(*this) + iFromIndex));

		LPCTYPE lpszText = chFind<TYPE, true>((LPCTYPE)(*this) + iFromIndex, ch);
		if(lpszText == NULL)
			return -1;
		return lpszText - m_aT;
	}
	int Find(LPCTYPE lpszSub, int iFromIndex) const
	{
		chASSERT(iFromIndex >= 0 && GetLength() >= iFromIndex);
		chASSERT(!chIsTrailbyte((LPCTYPE)(*this), (LPCTYPE)(*this) + iFromIndex));

		LPCTYPE lpszText = chFind<TYPE, true>((LPCTYPE)(*this) + iFromIndex, lpszSub);
		if(lpszText == NULL)
			return -1;
		return lpszText - m_aT;
	}

	void Replace( TYPE chOld, TYPE chNew )
	{
		if(!IsEmpty())
		{
			LPTYPE pData = m_aT;
			for(; *pData != '\0'; pData = (LPTYPE)chStrinc(pData))
			{
				if(chOld == chWord(pData))
					*pData = chNew;
			}
		}
	}
	void Replace( LPCTYPE lpszOld, LPCTYPE lpszNew )
	{
		chASSERT(lpszOld != NULL && lpszNew != NULL);
		int nOldTextLen = chStrlen(lpszOld);
		int nNewTextLen = chStrlen(lpszNew);
		
		_chTString tmpString;
		LPCTYPE pData = *this;
		for(; ;)
		{
			LPCTYPE pDataNext = chFind<TYPE, true>(pData, lpszOld);
			if(pDataNext == NULL)
			{// not find tagtext then add all left
				tmpString += pData;
				break;
			}
			else
			{
				int nCopySize = pDataNext - pData;
				int nOldSize = tmpString.GetLength();
				tmpString.AllocTextSizeWithEnding(nOldSize + nCopySize);
				tmpString.m_nTextLength = nOldSize + nCopySize;
				chStrncpy(tmpString.m_aT + nOldSize, pData, nCopySize);

				tmpString += lpszNew;
				pData = pData + nCopySize + nOldTextLen;
			}
		}
		CutAssign(tmpString);
	}

	int FindOneOf(LPCTYPE lpszSubText, int iFromIndex)
	{
		chASSERT(iFromIndex >= 0 && GetLength() >= iFromIndex && lpszSubText != NULL);
		if(IsEmpty())
			return -1;
		chASSERT(!chIsTrailbyte(m_aT, m_aT + iFromIndex));
		
		LPCTYPE lpszText = chFindOneOf<TYPE, true>(m_aT + iFromIndex, lpszSubText);
		if(lpszText == NULL)
			return -1;
		return lpszText - m_aT;
	}
	int ReverseFindOneOf(LPCTYPE lpszSubText)
	{
		chASSERT(lpszSubText != NULL);
		
		LPCTYPE lpszText = (LPCTYPE)(*this);
		LPCTYPE lpszEnd = lpszText + GetLength();
		lpszEnd = chResverFindOneOf<TYPE, true>(lpszText, lpszEnd, lpszSubText);
		if(lpszEnd == NULL)
			return -1;
		return lpszEnd - lpszText;
	}
	_chTString& DeleteWords(int iIndex, int nCount)
	{
		chASSERT(iIndex >= 0 && iIndex <= GetLength());
		
		if(!IsEmpty() && nCount > 0)
		{
			LPCTYPE lpszRemoveTo = m_aT + iIndex;
			chASSERT(!chIsTrailbyte(m_aT, lpszRemoveTo));
			
			for(;nCount > 0 && lpszRemoveTo != NULL; nCount--)
				lpszRemoveTo = chStrinc(lpszRemoveTo);
			if(lpszRemoveTo == NULL)
				lpszRemoveTo = m_aT + GetLength();
			
			RemoveAt(iIndex, lpszRemoveTo - m_aT - iIndex);
		}
		return *this;
	}
	_chTString& ReverseDeleteWords(int nCount)
	{
		chASSERT(nCount >= 0 && nCount <= GetLength());
		LPCTYPE lpszRemoveFrom = m_aT + GetLength();
		for(;nCount > 0 && lpszRemoveFrom != NULL; nCount--)
			lpszRemoveFrom = chStrdec(m_aT, lpszRemoveFrom);
		if(lpszRemoveFrom == NULL)
			lpszRemoveFrom = m_aT;

		RemoveAt(lpszRemoveFrom - m_aT, m_aT + GetLength() - lpszRemoveFrom);
		return *this;
	}
	_chTString& RemoveOneOf(LPCTYPE lpszSubText)
	{
		chASSERT(!chIS_EMPTY(lpszSubText));
		int iIndexFrom = FindOneOf(lpszSubText, 0);
		while(iIndexFrom != -1)
		{
			DeleteWords(iIndexFrom, 1);
			iIndexFrom = FindOneOf(lpszSubText, iIndexFrom);
		}
		return *this;
	}
	_chTString& TrimLeft(LPCTYPE lpszRemove)
	{
		chASSERT(lpszRemove != NULL);
		LPCTYPE lpszBegin = (LPCTYPE)(*this);
		LPCTYPE lpszFirst = chSkipOneOf<TYPE, true>(lpszBegin, lpszRemove);
		RemoveAt(0, lpszFirst - lpszBegin);
		return *this;
	}
	_chTString& TrimRight(LPCTYPE lpszRemove)
	{
		chASSERT(lpszRemove != NULL);
		LPCTYPE lpszBegin = (LPCTYPE)(*this);
		LPCTYPE lpszEnd = lpszBegin + GetLength();
		LPCTYPE lpszLast = chResverSkipOneOf<TYPE, true>(lpszBegin, lpszEnd, lpszRemove);
		RemoveAt(lpszLast - lpszBegin, lpszEnd - lpszLast);
		return *this;
	}
	_chTString MidWords(int iFirst, int nCount)
	{
		chASSERT(iFirst >= 0 && iFirst <= GetLength());
		
		_chTString strText;
		if(!IsEmpty() && nCount > 0)
		{
			LPCTYPE lpszCopyTo = m_aT + iFirst;
			chASSERT(!chIsTrailbyte(m_aT, lpszCopyTo));
			
			for(;nCount > 0 && lpszCopyTo != NULL; nCount--)
				lpszCopyTo = chStrinc(lpszCopyTo);
			if(lpszCopyTo == NULL)
				lpszCopyTo = m_aT + GetLength();
			
			int nCopySize = lpszCopyTo - m_aT - iFirst;
			strText.AllocTextSizeWithEnding(nCopySize);
			CopyObjects(strText.m_aT, m_aT + iFirst, nCopySize);
		}
		return strText;
	}
	_chTString RightWords(int nCount)
	{
		chASSERT(nCount >= 0 && nCount <= GetLength());
		
		LPCTYPE lpszCopyFrom = m_aT + GetLength();
		for(;nCount > 0 && lpszCopyFrom != NULL; nCount--)
			lpszCopyFrom = chStrdec(m_aT, lpszCopyFrom);
		if(lpszCopyFrom == NULL)
			lpszCopyFrom = m_aT;

		_chTString strText;
		int nCopySize = m_aT + GetLength() - lpszCopyFrom;
		strText.AllocTextSizeWithEnding(nCopySize);
		CopyObjects(strText.m_aT, lpszCopyFrom, nCopySize);
		return strText;
	}

	_chTString& MakeUpper()
	{
		if(!IsEmpty())
		{
			LPCTYPE pData = m_aT;
			for(; *pData != '\0'; pData = chStrinc(pData))
			{
				if(!chIsLeadbyte(pData))
					*pData = chUpper(*pData);
			}
		}
		return *this;
	}
	_chTString& MakeLower()
	{
		if(!IsEmpty())
		{
			LPTYPE pData = m_aT;
			for(; *pData != '\0'; pData = (LPTYPE)chStrinc(pData))
			{
				if(!chIsLeadbyte(pData))
					*pData = chLower(*pData);
			}
		}
		return *this;
	}
	_chTString& MakeReverse()
	{
		if(!IsEmpty())
		{
			LPCTYPE pData = m_aT;
			for(; *pData != '\0'; pData = chStrinc(pData))
			{
				if(!chIsLeadbyte(pData))
					*pData = chReverse(*pData);
			}
		}
		return *this;
	}

	_chTString& Insert(int iIndex, LPCTYPE lpszText)
	{
		chASSERT(iIndex >= 0 && iIndex <= GetLength() && lpszText != NULL);
		chASSERT(!chIsTrailbyte((LPCTYPE)(*this), (LPCTYPE)(*this) + iIndex));
		if(*lpszText != '\0')
		{
			int nInsertCount = chStrlen(lpszText);
			int nOldLength = GetLength();
			AllocTextSizeWithEnding(nOldLength + nInsertCount);
			m_nTextLength = nOldLength + nInsertCount;
			
			CopyObjects(m_aT + iIndex + nInsertCount, m_aT + iIndex, nOldLength - iIndex);
			CopyObjects(m_aT + iIndex, lpszText, nInsertCount);
		}
		return *this;
	}
	_chTString& Insert(int iIndex, const TYPE& ch)
	{
		chASSERT(iIndex >= 0 && iIndex <= GetLength());
		chASSERT(!chIsTrailbyte((LPCTYPE)(*this), (LPCTYPE)(*this) + iIndex));
		
		int nOldLength = GetLength();
		AllocTextSizeWithEnding(nOldLength + 1);
		m_nTextLength = nOldLength + 1;

		CopyObjects(m_aT + iIndex + 1, m_aT + iIndex, nOldLength - iIndex);
		m_aT[iIndex] = ch;
		return *this;
	}

	//////////////////////////////////////////////////////////////////////////
	// operactor 
	TYPE& operator[] (int iIndex) const
	{
		chASSERT(iIndex <= type_TYPEArray::GetSize());
		return m_aT[iIndex];
	}
	_chTString& operator += (LPCTYPE lpszText)
	{
		if(*lpszText == '\0')
			return *this;

		int nAddSize = chStrlen(lpszText);
		int nOldSize = GetLength();
		int nNewSize = nOldSize + chStrlen(lpszText);
		AllocTextSizeWithEnding(nNewSize);
		m_nTextLength = nNewSize;
		
		chStrncpy(m_aT + nOldSize, lpszText, nAddSize);
		return *this;
	}
	_chTString& operator += (const TYPE& ch)
	{
		if(ch != '\0')
		{
			int nOldSize = GetLength();
			int nNewSize = nOldSize + 1;
			AllocTextSizeWithEnding(nNewSize);
			m_nTextLength = nNewSize;
			
			m_aT[nOldSize] = ch;
		}
		return *this;
	}
	int Compare(LPCTYPE lpszText) const
	{
		chASSERT(lpszText != NULL);
		return chStrcmp<TYPE, true>((LPCTYPE)(*this), lpszText);
	}
	int CompareNoCase(LPCTYPE lpszText) const
	{
		chASSERT(lpszText != NULL);
		return chStrcmp<TYPE, false>((LPCTYPE)(*this), lpszText);
	}
//	BOOL operator == (LPCTYPE lpszText) const
//	{
//		chASSERT(lpszText != NULL);
//		return CompareNoCase(lpszText) == 0;
//	}
	BOOL operator > (LPCTYPE lpszText) const
	{
		chASSERT(lpszText != NULL);
		return CompareNoCase(lpszText) > 0;
	}

	BOOL operator < (LPCTYPE lpszText) const
	{
		chASSERT(lpszText != NULL);
		return CompareNoCase(lpszText) < 0;
	}
	//////////////////////////////////////////////////////////////////////////
	// format
	_chTString& __cdecl Format(LPCTYPE lpszFormat, ...)
	{
		chASSERT(lpszFormat != NULL);
		va_list argList;
		va_start(argList, lpszFormat);
		chVERIFY(FormatV(lpszFormat, argList));
		va_end(argList);
		return *this;
	}
	
	BOOL FormatV(LPCTYPE lpszFormat, va_list argList)
	{
		int nMaxLen = etlPreFormatString<TYPE>(lpszFormat, argList);
		AllocTextSizeWithEnding(nMaxLen);
		int nActualSize = chVsprintf(m_aT, lpszFormat, argList);
		chASSERT(nActualSize <= nMaxLen && nActualSize >= 0);
		AllocTextSizeWithEnding(nActualSize);
		m_nTextLength = nActualSize;
		return TRUE;
	}
	_chTString& CopyTextFrom(LPCTYPE lpszText, int nSize)
	{
		chASSERT(nSize >= 0);
		if(lpszText != NULL)
		{
			AllocTextSizeWithEnding(nSize);
			m_nTextLength = nSize;
			chStrncpy(m_aT, lpszText, nSize);
		}
		else
		{
			Clear();
		}
		return *this;
	}

	_chTString& AllocTextSizeWithEnding(int nNewSize)
	{
		chASSERT(nNewSize >= 0);
		type_TYPEArray::SetArraySize(nNewSize+1);
		type_TYPEArray::m_aT[nNewSize] = '\0';
		return *this;
	}
	_chTString& RepeatFill(const TYPE& ch, const int nRepeat)
	{
		chASSERT(nRepeat >= 0);
		AllocTextSizeWithEnding(nRepeat);
		int i;
		for(i = 0; i < nRepeat; i++ )
			m_aT[i] = ch;
		m_nTextLength = (ch == '\0') ? 0 : nRepeat;
		return *this;
	}
	_chTString& RemoveAt(int iIndex, int nCount)
	{
		chASSERT(iIndex >= 0 && iIndex <= GetLength());
		
		if(!IsEmpty() && nCount > 0)
		{
			LPCTYPE lpszRemoveFrom = m_aT + iIndex;
			chASSERT(!chIsTrailbyte(m_aT, lpszRemoveFrom));
			nCount = chMIN(GetLength() - iIndex, nCount);
			int nCopySize = GetLength() - iIndex - nCount;

			CopyObjects(m_aT + iIndex, lpszRemoveFrom + nCount, nCopySize); 
			int nNewSize = iIndex + nCopySize;
			AllocTextSizeWithEnding(nNewSize);
			m_nTextLength = nNewSize;
		}
		return *this;
	}
protected:	_chTString& CopyAssign(const _chTString& srcString)
	{
		type_TYPEArray::CopyAssign(srcString);
		m_nTextLength = srcString.m_nTextLength;
		return *this;
	}
	_chTString& CutAssign(_chTString& srcString)
	{
		type_TYPEArray::CutAssign(srcString);
		m_nTextLength = srcString.m_nTextLength;
		srcString.m_nTextLength = 0;
		return *this;
	}
	_chTString& CopyFrom(LPCTYPE lpszText)
	{
		if(lpszText != NULL)
		{
			int nNewLength = chStrlen(lpszText);
			AllocTextSizeWithEnding(nNewLength);
			m_nTextLength = nNewLength;
			
			chStrncpy(m_aT, lpszText, nNewLength);
		}
		else
		{
			Clear();
		}
		return *this;
	}
	BOOL operator == (LPCTYPE lpszText) const
	{
		chASSERT(lpszText != NULL);
		return CompareNoCase(lpszText) == 0;
	}
private:
	_chTString(_chTString& srcString)
	{
		CutAssign(srcString);
	}
public:
	_chTString() : m_nTextLength(0){}
	~_chTString() { Clear(); }
protected:
	int m_nTextLength; // means real size with zero ending, -1 means length has't calculated 
};

#pragma warning( push )
#pragma warning(disable : 4521)

template <class TYPE>
class _chTempString : public _chTString<TYPE>
{
public:
	_chTempString(){}
	_chTempString(const _chTempString& srcString);
//	{
//		chASSERT(FALSE);
//	}
	_chTempString(_chTempString& srcString)
	{
		CutAssign(srcString);
	}
	_chTempString& operator = (_chTempString& srcString)
	{ 
		CutAssign(srcString);
		return *this;
	}
	_chTempString& operator = (LPCTYPE lpszText)
	{
		CopyFrom(lpszText);
		return *this;
	}
public:
	_chTempString(const TYPE& ch, const int nRepeat)
	{
		RepeatFill(ch, nRepeat);
	}
	_chTempString(LPCTYPE lpszFormat, ...)
	{
		if(lpszFormat != NULL)
		{
			va_list argList;
			va_start(argList, lpszFormat);
			chVERIFY(FormatV(lpszFormat, argList));
			va_end(argList);
		}
	}
};
#pragma warning( pop ) 

template <class TYPE>
class _chString : public _chTString<TYPE>
{
public:
	_chString(){}
	_chString(const _chString& srcString)
	{
		CopyAssign(srcString);
	}
	_chString& operator = (const _chString& srcString)
	{
		CopyAssign(srcString);
		return *this;
	}
	_chString& operator = (LPCTYPE lpszText)
	{
		CopyFrom(lpszText);
		return *this;
	}
public:
	_chString(const TYPE& ch, const int nRepeat)
	{ RepeatFill(ch, nRepeat); }
	_chString(LPCTYPE lpszFormat, ...)
	{
		if(lpszFormat != NULL)
		{
			va_list argList;
			va_start(argList, lpszFormat);
			chVERIFY(FormatV(lpszFormat, argList));
			va_end(argList);
		}
	}
};

typedef _chTempString<TCHAR>	chTempString;
typedef _chString<TCHAR>		chString;

typedef _chTempString<CHAR>		chTempStringA;
typedef _chString<CHAR>			chStringA;

typedef _chTempString<WCHAR>	chTempStringW;
typedef _chString<WCHAR>		chStringW;

//};
//using namespace ETL_StringHelper;

#endif //__ETLSTRING_H__
